# -*- coding: utf-8 -*-
# 参考文章 使用python爬虫抓站的一些技巧总结：进阶篇 http://obmem.info/?p=753
# 2、设定线程的栈大小
# 栈大小的设定将非常显著地影响python的内存占用，python多线程不设置这个值会导致程序占用大量内存，这对openvz的vps来说非常致命。stack_size必须大于32768，实际上应该总要32768*2以上
from threading import stack_size
stack_size(32768*16)

# 4、设置超时
import socket
socket.setdefaulttimeout(10) #设置10秒后连接超时

# 一、gzip/deflate支持
# 现在的网页普遍支持gzip压缩，这往往可以解决大量传输时间，以VeryCD的主页为例，未压缩版本247K，压缩了以后45K，为原来的1/5。这就意味着抓取速度会快5倍。
# 然而python的urllib/urllib2默认都不支持压缩，要返回压缩格式，必须在request的header里面写明’accept-encoding’，然后读取response后更要检查header查看是否有’content-encoding’一项来判断是否需要解码，很繁琐琐碎。如何让urllib2自动支持gzip, defalte呢？
# 其实可以继承BaseHanlder类，然后build_opener的方式来处理：
import urllib2
from gzip import GzipFile
from StringIO import StringIO
class ContentEncodingProcessor(urllib2.BaseHandler):
  # """A handler to add gzip capabilities to urllib2 requests """
 
  # add headers to requests
  def http_request(self, req):
    req.add_header("Accept-Encoding", "gzip, deflate")
    return req
 
  # decode
  def http_response(self, req, resp):
    old_resp = resp
    # gzip
    if resp.headers.get("content-encoding") == "gzip":
			gz = GzipFile(
				fileobj=StringIO(resp.read()),
				mode="r"
			)
			resp = urllib2.addinfourl(gz, old_resp.headers, old_resp.url, old_resp.code)
			resp.msg = old_resp.msg
    # deflate
    if resp.headers.get("content-encoding") == "deflate":
			gz = StringIO( deflate(resp.read()) )
			resp = urllib2.addinfourl(gz, old_resp.headers, old_resp.url, old_resp.code)  # 'class to add info() and
			resp.msg = old_resp.msg
    return resp
# 如何使用
# encoding_support = ContentEncodingProcessor
# opener = urllib2.build_opener( encoding_support, urllib2.HTTPHandler )
# content = opener.open(url).read() # 直接用opener打开网页，如果服务器支持gzip/defalte则自动解压缩

		
# deflate support
import zlib
def deflate(data):   # zlib only provides the zlib compress format, not the deflate format;
  try:               # so on top of all there's this workaround:
    return zlib.decompress(data, -zlib.MAX_WBITS)
  except zlib.error:
    return zlib.decompress(data)

# Fetcher类，你可以这么调用
# f = Fetcher(threads=10) #设定下载线程数为10
# for url in urls:
    # f.push(url)  #把所有url推入下载队列
# while f.taskleft(): #若还有未完成下载的线程
    # content = f.pop()  #从下载完成队列中取出结果
    # do_with(content) # 处理content内容
# 这么个多线程调用简单明了，那么就这么设计吧，首先要有两个队列，用Queue搞定，多线程的基本架构也和“技巧总结”一文类似，push方法和pop方法都比较好处理，都是直接用Queue的方法，taskleft则是如果有“正在运行的任务”或者”队列中的任务”则为是，也好办，于是代码如下
import urllib2
from threading import Thread,Lock
from Queue import Queue
import time
class Fetcher:
	def __init__(self,threads):
		encoding_support = ContentEncodingProcessor
		self.opener = urllib2.build_opener(encoding_support,urllib2.HTTPHandler)
		self.lock = Lock() #线程锁
		self.q_req = Queue() #任务队列
		self.q_ans = Queue() #完成队列
		self.threads = threads
		for i in range(threads):
			t = Thread(target=self.threadget)
			t.setDaemon(True)
			t.start()
		self.running = 0
 
	def __del__(self): #解构时需等待两个队列完成
		time.sleep(0.5)
		self.q_req.join()
		self.q_ans.join()

	def taskleft(self):
		return self.q_req.qsize()+self.q_ans.qsize()+self.running

	def push(self,req):
		self.q_req.put(req)

	def pop(self):
		return self.q_ans.get()

	def threadget(self):
		while True:
			req = self.q_req.get()
			with self.lock: #要保证该操作的原子性，进入critical area
				self.running += 1
			try:
				ans = self.opener.open(req).read()
			except Exception, what:
				ans = ''
				print what
			self.q_ans.put((req,ans))
			with self.lock:
				self.running -= 1
			self.q_req.task_done()
			time.sleep(0.1) # don't spam
	def get(self,req,retries=3): #设置失败后自动重试
		try:
			response = self.opener.open(req)
			data = response.read()
		except Exception , what:
			print what,req
			if retries>0:
				return self.get(req,retries-1)
			else:
				print 'GET Failed',req
				return ''
		return data
# 登陆更加简化了，首先build_opener中要加入cookie支持，参考“总结”一文；如要登陆VeryCD，给Fetcher新增一个空方法login，并在__init__()中调用，然后继承Fetcher类并override login方法：
# 于是在Fetcher初始化时便会自动登录VeryCD网站。
# def login(self,username,password):
	# import urllib
	# data=urllib.urlencode({'username':username,
												 # 'password':password,
												 # 'continue':'http://www.verycd.com/',
												 # 'login_submit':u'登录'.encode('utf-8'),
												 # 'save_cookie':1,})
	# url = 'http://www.verycd.com/signin'
	# self.opener.open(url,data).read()
	
# ####################################################
# 主程序
# ####################################################
if __name__ == "__main__":
	links = [ 'http://www.nbedi.com/h2000eport_ic/pre_bgd/EntryLogDataSearch/bgd_print/normal_print.asp?sign=1&pre_entry_id=310420090%d'%i for i in range(541461000,541461094) ]
	print links
	f = Fetcher(threads=10) #设定下载线程数为10
	for url in links:
		f.push(url) #把所有url推入下载队列
	while f.taskleft(): #若还有未完成下载的线程
		url,content = f.pop() #从下载完成队列中取出结果
		print url,len(content) # 处理content内容


